import re
from urllib.parse import *

__all__ = ('Segment', 'StaticSegment', 'DynamicSegment', 'PathSegment', 'Part',)

class Segment:
    @staticmethod
    def parse(part):
        seg_re = re.compile(r'\{\s*(.+?)\s*\}', re.A)
        start = 0
        segments = []
        while start < len(part):
            m = seg_re.search(part, start)
            if(m):
                if m.start() != start:
                    segments.append(StaticSegment(part[start:m.start()]))
                key = m.group(1).strip()
                for parser in Segment.parsers:
                    m_parse = re.match(parser.regex, key)
                    if m_parse:
                        segments.append(parser(*m_parse.groups()))
                        break
                else:
                    raise ValueError('unrecognizable segment: ' + m.group())
                    
                start = m.end()
            else:
                segments.append(StaticSegment(part[start:]))
                break
        return segments

    def __init__(self):
        self.optional = False


class StaticSegment(Segment):
    def __init__(self, value):
        super().__init__()
        self.value = value
        self.escaped_value = quote(value)

    def __str__(self):
        return self.value

    @property
    def regex_chunk(self):
        return re.escape(self.value)

    def match(self, path, start, result):
        return path.startswith(self.value, start), start + len(self.value)
    
    def reverse(self, values, discard_default=True):
        return self.escaped_value


class DynamicSegment(Segment):
    regex = r'(.*)'

    def __init__(self, key):
        super().__init__()
        self.key = key
        self.constraint = None

    def set_default(self, value):
        self._default_str = '' if value is None else str(value)
        self._escaped_default_str = quote(self._default_str)
        self.optional = True

    def __str__(self):
        return '{' + self.key + '}'

    @property
    def regex_chunk(self):
        chunk = '(?P<{0}>{1})'.format(self.key, self.constraint or '.+')
        if self.optional:
            chunk += '?'
        return chunk

    def reverse(self, values, discard_default=True):
        value = values.pop(self.key, None)
        if self.optional:
            # value should have been converted to str before
            if not value or value == self._default_str:
                return '' if discard_default else self._escaped_default_str
        # value should not be None here, because Route.reverse will check required_keys
        return quote(value)


class PathSegment(DynamicSegment):
    regex = r'\*\s*(.*)'

    def __init__(self, key):
        super().__init__(key)
        self.set_default('')

    def __str__(self):
        return '{*' + self.key + '}'

    @property
    def regex_chunk(self):
        return '(?P<{0}>.*)'.format(self.key)

Segment.parsers = [PathSegment, DynamicSegment]


class Part:
    def __init__(self, segments):
        self.segments = tuple(segments)

        path_segments = [s for s in self.segments if type(s) is PathSegment]
        if len(path_segments) == 0:
            self.contains_path_segment = False
        elif len(path_segments) == 1:
            if not segments[-1] is path_segments[0]:
                raise ValueError()
            self.contains_path_segment = True
        else:
            raise ValueError()

    def compile(self):
        self.regex = re.compile(''.join([s.regex_chunk for s in self.segments]) + '$', re.I)
        self.optional = len([s for s in self.segments if not s.optional]) == 0

    def __str__(self):
        return ''.join([str(s) for s in self.segments])


